/**********************************************************************************
***
***    Screencasting Teaching System C++ Library
***    Copyright (C) 2011  Jiri Novak <jiri.novak@petriny.net>
***                        Wadi Jalil Maluf <wadijm@gmail.com>
***
***    This file is part of the Screencasting Teaching System C++ Library.
***
***    This library is free software; you can redistribute it and/or modify
***    it under the terms of the GNU General Public License as published by
***   the Free Software Foundation; either version 3 of the License, or
***    (at your option) any later version.
***
***    This library is distributed in the hope that it will be useful,
***    but WITHOUT ANY WARRANTY; without even the implied warranty of
***    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
***   GNU General Public License for more details.
***
***    You should have received a copy of the GNU General Public License
***    along with this library; if not, write to the Free Software
***    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
***
*********************************************************************************/

#include "xmlutil.h"
#include <cstdlib>
#include <cassert>
#include <algorithm>
#include <iostream>

/**
 * Constructor. Initializes logger.
 */
XmlUtil::XmlUtil() :
	logger_(log4cxx::Logger::getLogger("common.xml_util")) {
}

/**
 * Destructor.
 */
XmlUtil::~XmlUtil() {
}

/**
 * Parses and creates ChatMessage domain object from its XML representation.
 *
 * @param xmlStr XML representation of the persisted instance
 * @return Pointer to ChatMessage instance corresponding to the XML representation given
 */
ChatMessage* XmlUtil::parseChatMessage(const std::string &xmlStr) {
	LOG4CXX_TRACE(logger_, "parsing chat message");

	ChatMessage* chatMessage = NULL;
	xmlpp::DomParser parser;
	parser.parse_memory_raw((const unsigned char *) xmlStr.c_str(), xmlStr.size());
	if (parser)
		chatMessage = parseChatMessageNode(parser.get_document()->get_root_node());
	return chatMessage;
}

/**
 * Given the messageNode of XML representation creates, populates and returns pointer to ChatMessage instance.
 *
 * @param messageNode containing XML persisted instance information
 * @return Pointer to ChatMessage instance corresponding to the XML representation given
 */
ChatMessage* XmlUtil::parseChatMessageNode(xmlpp::Node *messageNode) {
	LOG4CXX_TRACE(logger_, "parsing chat message node");

	ChatMessage *msg = new ChatMessage();

	const xmlpp::Element *root = dynamic_cast<const xmlpp::Element*> (messageNode);
	msg->setMessage(root->get_attribute_value("msg"));
	msg->setUser(root->get_attribute_value("user"));
	msg->setLesson(std::atoi(root->get_attribute_value("les").c_str()));

	return msg;
}

/**
 * Parses and creates User domain object from its XML representation.
 *
 * @param xmlStr XML representation of the persisted instance
 * @return Pointer to User instance corresponding to the XML representation given
 */
User* XmlUtil::parseUser(const std::string &xmlStr) {
	LOG4CXX_TRACE(logger_, "parsing user");

	User* user = NULL;
	xmlpp::DomParser parser;
	parser.parse_memory_raw((const unsigned char *) xmlStr.c_str(), xmlStr.size());
	if (parser)
		user = parseUserNode(parser.get_document()->get_root_node());
	return user;
}

/**
 * Parses <user> XML tag, creates User domain object instance
 *
 * @param userNode node from which should be read
 * @return Pointer to User instance corresponding to the XML representation given
 */
User* XmlUtil::parseUserNode(xmlpp::Node *userNode) {
	LOG4CXX_TRACE(logger_, "parsing user node");

	User* user = new User();
	const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*> (userNode);

	LOG4CXX_TRACE(logger_, "* parsing user node name = " << root->get_attribute_value("name").c_str());
	user->setName(root->get_attribute_value("name"));
	LOG4CXX_TRACE(logger_, "* parsing user node id = " << root->get_attribute_value("id").c_str());
	user->setId(atoi(root->get_attribute_value("id").c_str()));
	LOG4CXX_TRACE(logger_, "* parsing user node instructor = " << root->get_attribute_value("instructor").c_str());
	user->setInstructor(root->get_attribute_value("instructor") == "true");
	LOG4CXX_TRACE(logger_, "* parsing user node password = " << root->get_attribute_value("password").c_str());
	user->setPassword(root->get_attribute_value("password"));
	LOG4CXX_TRACE(logger_, "* parsing user node username = " << root->get_attribute_value("username").c_str());
	user->setUser(root->get_attribute_value("username"));

	return user;
}

/**
 * Parses <shows> XML tag, creates Lesson and Course domain objects, and appends pointers to them
 * into the specified lists
 *
 * @param xmlStr XML representation of the persisted instances
 * @param courses list of pointers to the Course instances where the read instance will be appended
 * @param lessons list of pointers to the Lesson instances where the read instance will be appended
 */
void XmlUtil::parseShows(const std::string &xmlStr, std::list<Course*> &courses, std::list<Lesson*> &lessons) {
	LOG4CXX_TRACE(logger_, "parsing courses and lessons");

	xmlpp::DomParser parser;
	parser.parse_memory_raw((const unsigned char *) xmlStr.c_str(), xmlStr.size());
	parseCoursesNode(parser.get_document()->get_root_node(), courses);
	parseLessonsNode(parser.get_document()->get_root_node(), lessons);
}

/**
 * Parses <lesson> XML tag, creates Lesson domain object instance
 *
 * @param lessonsNode node from which should be read
 * @return pointer to the Lesson instances where the read instance will be appended
 */
Lesson* XmlUtil::parseLessonNode(xmlpp::Node *lessonNode) {
	LOG4CXX_TRACE(logger_, "parsing lesson node");

	const xmlpp::Element* node = dynamic_cast<const xmlpp::Element*> (lessonNode);
	Lesson* lesson = new Lesson();

	LOG4CXX_TRACE(logger_, "* parsing lesson node id = " << node->get_attribute_value("id").c_str());
	lesson->setId(atoi(node->get_attribute_value("id").c_str()));
	LOG4CXX_TRACE(logger_, "* parsing lesson node name = " << node->get_attribute_value("name").c_str());
	lesson->setName(node->get_attribute_value("name"));
	LOG4CXX_TRACE(logger_, "* parsing lesson node desc = " << node->get_attribute_value("desc").c_str());
	lesson->setDescription(node->get_attribute_value("desc"));
	LOG4CXX_TRACE(logger_, "* parsing lesson node date = " << node->get_attribute_value("date").c_str());
	lesson->setProgrammedDate(getTime(node->get_attribute_value("date")));
	LOG4CXX_TRACE(logger_, "* parsing lesson node key = " << node->get_attribute_value("key").c_str());
	lesson->setKey(node->get_attribute_value("key"));
	LOG4CXX_TRACE(logger_, "* parsing lesson node code = " << node->get_attribute_value("code").c_str());
	lesson->setCode(node->get_attribute_value("code"));

	// subscribed students
	xmlpp::NodeSet studentsList = node->find("./students/user");
	for (xmlpp::NodeSet::iterator iter = studentsList.begin(); iter != studentsList.end(); ++iter) {
		User* l = parseUserNode(*iter);
		lesson->getStudents().push_back(l);
	}

	// instructors
	xmlpp::NodeSet instructorsList = node->find("./instructors/user");
	for (xmlpp::NodeSet::iterator iter = instructorsList.begin(); iter != instructorsList.end(); ++iter) {
		User* l = parseUserNode(*iter);
		lesson->getInstructors().push_back(l);
	}

	return lesson;
}

/**
 * Parses <course> XML tag, creates Course domain object instance
 *
 * @param courseNode node from which should be read
 * @return pointer to the Course instances where the read instance will be appended
 */
Course* XmlUtil::parseCourseNode(xmlpp::Node *courseNode) {
	LOG4CXX_TRACE(logger_, "parsing course node");

	xmlpp::Element* node = dynamic_cast<xmlpp::Element*> (courseNode);
	Course* course = new Course();

	LOG4CXX_TRACE(logger_, "* parsing course node id = " << node->get_attribute_value("id").c_str());
	course->setId(atoi(node->get_attribute_value("id").c_str()));
	LOG4CXX_TRACE(logger_, "* parsing course node name = " << node->get_attribute_value("name").c_str());
	course->setName(node->get_attribute_value("name"));
	LOG4CXX_TRACE(logger_, "* parsing course node desc = " << node->get_attribute_value("desc").c_str());
	course->setDescription(node->get_attribute_value("desc"));
	LOG4CXX_TRACE(logger_, "* parsing course node startDate = " << node->get_attribute_value("startDate").c_str());
	course->setStartDate(getTime(node->get_attribute_value("startDate")));
	LOG4CXX_TRACE(logger_, "* parsing course node endDate = " << node->get_attribute_value("endDate").c_str());
	course->setEndDate(getTime(node->get_attribute_value("endDate")));
	LOG4CXX_TRACE(logger_, "* parsing course node key = " << node->get_attribute_value("key").c_str());
	course->setKey(node->get_attribute_value("key"));
	LOG4CXX_TRACE(logger_, "* parsing course node code = " << node->get_attribute_value("code").c_str());
	course->setCode(node->get_attribute_value("code"));

	// consisting lessons
	parseLessonsNode(node, course->getLessons());

	// subscribed students
	xmlpp::NodeSet studentsList = node->find("./students/user");
	for (xmlpp::NodeSet::iterator iter = studentsList.begin(); iter != studentsList.end(); ++iter) {
		User* l = parseUserNode(*iter);
		course->getStudents().push_back(l);
	}

	// instructors
	xmlpp::NodeSet instructorsList = node->find("./instructors/user");
	for (xmlpp::NodeSet::iterator iter = instructorsList.begin(); iter != instructorsList.end(); ++iter) {
		User* l = parseUserNode(*iter);
		course->getInstructors().push_back(l);
	}

	return course;
}

/**
 * Parses and creates Lesson domain object from its XML representation.
 *
 * @param xmlStr XML representation of the persisted instance
 * @return Pointer to Lesson instance corresponding to the XML representation given
 */
Lesson* XmlUtil::parseLesson(const std::string &xmlStr) {
	LOG4CXX_TRACE(logger_, "parsing lesson");

	xmlpp::DomParser parser;
	parser.parse_memory_raw((const unsigned char *) xmlStr.c_str(), xmlStr.size());
	return parseLessonNode(parser.get_document()->get_root_node());
}

/**
 * Parses and creates Course domain object from its XML representation.
 *
 * @param xmlStr XML representation of the persisted instance
 * @return Pointer to Course instance corresponding to the XML representation given
 */
Course* XmlUtil::parseCourse(const std::string &xmlStr) {
	LOG4CXX_TRACE(logger_, "parsing course");

	xmlpp::DomParser parser;
	parser.parse_memory_raw((const unsigned char *) xmlStr.c_str(), xmlStr.size());
	return parseCourseNode(parser.get_document()->get_root_node());
}


/**
 * Parses <lessons> XML tag, creates Lesson domain object instance, and appends pointer to that into
 * the specified list.
 *
 * @param lessonsNode node from which should be read
 * @param list list of pointers to the Lesson instances where the read instance will be appended
 */
void XmlUtil::parseLessonsNode(xmlpp::Node* lessonsNode, std::list<Lesson*>& list) {
	LOG4CXX_TRACE(logger_, "parsing lessons node");

	xmlpp::NodeSet lessonsList = lessonsNode->find("./lessons/lesson");
	for (xmlpp::NodeSet::iterator iter = lessonsList.begin(); iter != lessonsList.end(); ++iter) {
		Lesson* l = parseLessonNode(*iter);
		list.push_back(l);
	}
}

/**
 * Parses <courses> XML tag, creates Course domain object instance, and appends pointer to that into
 * the specified list.
 *
 * @param coursesNode node from which should be read
 * @param list list of pointers to the Course instances where the read instance will be appended
 */
void XmlUtil::parseCoursesNode(xmlpp::Node *coursesNode, std::list<Course*> &list) {
	LOG4CXX_TRACE(logger_, "parsing courses node");

	xmlpp::NodeSet courseList = coursesNode->find("./courses/course");
	for (xmlpp::NodeSet::iterator iter = courseList.begin(); iter != courseList.end(); ++iter) {
		Course *c = parseCourseNode(*iter);
		list.push_front(c);
	}
}

/**
 * Serializes ChatMessage domain object into its XML representation so that it could be transfered
 * and persisted.
 *
 * @param
 * @return XML representation of chat message
 */
std::string XmlUtil::serializeChatMessage(ChatMessage &msg) {
	LOG4CXX_TRACE(logger_, "serializing chat message");

	xmlpp::Document doc;

	xmlpp::Element* root = doc.create_root_node("chatmsg");
	root->set_attribute("user", msg.getUser());
	root->set_attribute("msg", msg.getMessage());
	root->set_attribute("les", getStringFromInt(msg.getLesson()));

	return doc.write_to_string();
}

/**
 * Serializes User domain object into its XML representation so that it could be transfered and
 * persisted.
 *
 * @param user instance of user which we want to serialize
 * @return XML representation of user
 */
std::string XmlUtil::serializeUser(User &user) {
	LOG4CXX_TRACE(logger_, "serializing user");

	xmlpp::Document doc;

	doc.create_root_node("user");
	xmlpp::Node *root = doc.get_root_node();
	populateUserNode(root, user);

	return doc.write_to_string();
}

/**
 * Serializes complete offer of shows (courses and lessons which don't belong to any course)
 * so that it could be transfered and persisted.
 *
 * @param courses list of course instances that we want to serialize
 * @param lessons list of lesson instances (which don't belong to any course) that we want to serialize
 * @return XML representation of shows (courses and lessons which don't correspond to any course)
 */
std::string XmlUtil::serializeShows(std::list<Course*> &courses, std::list<Lesson*> &lessons) {
	LOG4CXX_TRACE(logger_, "serializing shows");

	xmlpp::Document doc;

	doc.create_root_node("shows");
	xmlpp::Node* root = doc.get_root_node();

	xmlpp::Node* coursesNode = root->add_child("courses");
	populateCoursesNode(coursesNode, courses);

	xmlpp::Node* lessonsNode = root->add_child("lessons");
	populateLessonsNode(lessonsNode, lessons);

	return doc.write_to_string();
}

/**
 * Populates the DOM's coursesNode with the given courses.
 *
 * @param coursesNode node to which we wish to append the result
 * @param coursesList list of courses instances that we wont to serialize
 */
void XmlUtil::populateCoursesNode(xmlpp::Node *coursesNode, const std::list<Course*> &coursesList) {
	LOG4CXX_TRACE(logger_, "populating courses node");

	for (std::list<Course*>::const_iterator iter = coursesList.begin(); iter != coursesList.end(); ++iter) {
		Course* course = (*iter);
		populateCourseNode(coursesNode->add_child("course"), *course);
	}
}

/**
 * Populates the DOM's courseNode with the given lesson.
 *
 * @param courseNode node to which we wish to append the result
 * @param course reference to the lesson instance we wish to serialize
 */
void XmlUtil::populateCourseNode(xmlpp::Node *courseNode, Course &course) {
	LOG4CXX_TRACE(logger_, "populating course node");

	xmlpp::Element *node = (xmlpp::Element*) courseNode;

	node->set_attribute("id", getStringFromInt(course.getId()));
	node->set_attribute("name", course.getName());
	node->set_attribute("desc", course.getDescription());
	node->set_attribute("startDate", getStrTime(course.getStartDate()));
	node->set_attribute("endDate", getStrTime(course.getEndDate()));
	if (!course.getKey().empty()) {
		node->set_attribute("key", course.getKey());
		node->set_attribute("code", course.getCode());
	}

	// instructors
	if (course.getInstructors().size() > 0) {
		xmlpp::Node *instructorsNode = node->add_child("instructors");

		for (std::list<User*>::const_iterator it = course.getInstructors().begin(); it != course.getInstructors().end(); ++it) {
			xmlpp::Node *userNode = instructorsNode->add_child("user");
			populateUserNode(userNode, *(*it));
		}
	}

	// students
	if (course.getStudents().size() > 0) {
		xmlpp::Node *studentsNode = node->add_child("students");

		for (std::list<User*>::const_iterator it = course.getStudents().begin(); it != course.getStudents().end(); ++it) {
			xmlpp::Node *userNode = studentsNode->add_child("user");
			populateUserNode(userNode, *(*it));
		}
	}

	// lessons included in this course
	xmlpp::Node *lessons = courseNode->add_child("lessons");
	populateLessonsNode(lessons, course.getLessons());
}

/**
 * Serialize lesson domain object so that it could be transfered and persisted.
 *
 * @return XML representation of lesson
 */
std::string XmlUtil::serializeLesson(Lesson &lesson) {
	LOG4CXX_TRACE(logger_, "serializing lesson");

	xmlpp::Document doc;

	doc.create_root_node("lesson");
	xmlpp::Node *root = doc.get_root_node();
	populateLessonNode(root, lesson);

	return doc.write_to_string();
}

/**
 * Serialize course domain object so that it could be transfered and persisted.
 *
 * @return XML representation of course
 */
std::string XmlUtil::serializeCourse(Course &course) {
	LOG4CXX_TRACE(logger_, "serializing course");

	xmlpp::Document doc;

	doc.create_root_node("course");
	xmlpp::Node *root = doc.get_root_node();
	populateCourseNode(root, course);

	return doc.write_to_string();
}

/**
 * Populates the DOM's lessonNode with the given lesson.
 *
 * @param lessonNode node to which we wish to append the result
 * @param lesson reference to the lesson instance we wish to serialize
 */
void XmlUtil::populateLessonNode(xmlpp::Node* lessonNode, Lesson &lesson) {
	LOG4CXX_TRACE(logger_, "populating lesson node");

	xmlpp::Element* node = (xmlpp::Element*) lessonNode;

	node->set_attribute("id", getStringFromInt(lesson.getId()));
	node->set_attribute("name", lesson.getName());
	node->set_attribute("desc", lesson.getDescription());
	node->set_attribute("date", getStrTime(lesson.getProgrammedDate()));
	if (!lesson.getKey().empty()) {
		node->set_attribute("key", lesson.getKey());
		node->set_attribute("code", lesson.getCode());
	}

	// instructors
	if (lesson.getInstructors().size() > 0) {
		xmlpp::Node *instructorsNode = node->add_child("instructors");

		for (std::list<User*>::const_iterator it = lesson.getInstructors().begin(); it != lesson.getInstructors().end(); ++it) {
			xmlpp::Node *userNode = instructorsNode->add_child("user");
			populateUserNode(userNode, *(*it));
		}
	}

	// students
	if (lesson.getStudents().size() > 0) {
		xmlpp::Node *studentsNode = node->add_child("students");

		for (std::list<User*>::const_iterator it = lesson.getStudents().begin(); it != lesson.getStudents().end(); ++it) {
			xmlpp::Node *userNode = studentsNode->add_child("user");
			populateUserNode(userNode, *(*it));
		}
	}
}

/**
 * Populates the DOM's userNode with the given user.
 *
 * @param userNode node to which we wish to append the result
 * @param user reference to the lesson instance we wish to serialize
 */
void XmlUtil::populateUserNode(xmlpp::Node *userNode, const User &user) {
	LOG4CXX_TRACE(logger_, "populating user node");

	xmlpp::Element* node = (xmlpp::Element*) userNode;

	node->set_attribute("id", getStringFromInt(user.getId()));
	if (!user.getName().empty()) {
		node->set_attribute("instructor", user.isInstructor() ? "true" : "false");
		node->set_attribute("name", user.getName());
		node->set_attribute("username", user.getUser());
		node->set_attribute("password", user.getPassword());
	}
}

/**
 * Populates the DOM's lessonsNode with the given lesson list.
 *
 * @param lessonsNode node to which we wish to append the result
 * @param lessonList list of the lesson instances we wish to serialize
 */
void XmlUtil::populateLessonsNode(xmlpp::Node* lessonsNode, const std::list<Lesson*> &lessonsList) {
	LOG4CXX_TRACE(logger_, "populating lessons node");

	for (std::list<Lesson*>::const_iterator iter = lessonsList.begin(); iter != lessonsList.end(); ++iter) {
		Lesson* lesson = (*iter);
		populateLessonNode(lessonsNode->add_child("lesson"), *lesson);
	}
}

/**
 * Auxiliary function for converting std::string to time_t structure.
 *
 * @param strData string representation of time
 * @return time_t representation of time
 */
time_t XmlUtil::getTime(const std::string &strData) {
	time_t t;
	std::stringstream ss;
	ss << strData;
	ss >> t;
	return t;
}

/**
 * Auxiliary function for converting time_t structure std::string.
 *
 * @param time_t representation of time
 * @return string representation of time
 */
std::string XmlUtil::getStrTime(time_t time) {
	std::string a;
	std::stringstream ss;
	ss << time;
	ss >> a;
	return a;
}

/**
 * Auxiliary function for converting number to its string equivalent
 *
 * @param number integer we want to convert
 * @return strData string representation of number
 */
std::string XmlUtil::getStringFromInt(int number) {
	std::stringstream strm;
	std::string id;
	strm << number;
	strm >> id;
	return id;
}

/**
 * Loads users from the XML file with the server configuration.
 *
 * @params usersFile input stream containing configuration of users
 * @params users list of pointers to User instances to be populated while parsing
 */
void XmlUtil::loadUsers(std::istream &usersFile, std::list<User*> &users) {
	LOG4CXX_TRACE(logger_, "loading users");

	xmlpp::DomParser parser;
	parser.parse_stream(usersFile);

	if (parser) {
		// get the "users" tag
		xmlpp::NodeSet coursesList = parser.get_document()->get_root_node()->find("//user");
		for (xmlpp::NodeSet::iterator iter = coursesList.begin(); iter != coursesList.end(); ++iter) {
			// parse individual users
			xmlpp::Element* userNode = dynamic_cast<xmlpp::Element*> (*iter);
			users.push_back(parseUserNode(userNode));
		}
	}

}

/**
 * Loads courses and lessons which don't belong to any course from the XML file with the server configuration.
 *
 * @params coursesFile input stream containing configuration of courses and lessons which don't belong to any course
 * @params courses list of pointers to Course instances to be populated while parsing
 * @params lessons list of pointers to Lesson instances which don't belong to any course to be populated while parsing
 */
void XmlUtil::loadShows(std::istream &coursesFile, std::list<Course*> &courses, std::list<Lesson*> &lessons) {
	LOG4CXX_TRACE(logger_, "loading courses and lessons");

	xmlpp::DomParser parser;
	parser.parse_stream(coursesFile);

	// parse courses
	parseCoursesNode(parser.get_document()->get_root_node(), courses);

	// parse lessons which don't belong to any course
	parseLessonsNode(parser.get_document()->get_root_node(), lessons);
}
